﻿/*
 Copyright (c) 2016, Robert Gaffney
 All rights reserved.
 Licensed under New BSD 2-clause license.

 Robert Gaffney 2016-08-05
 Here is the data model and controller for the memory game.  For a larger product, the model and controller
 are ideally split into separate classes and files.

 The game initializes with a welcome message to the user, an attempt count of 0, 12 tiles randomly
 assigned 1 of 6 glyphicon symbols (with each symbol assigned to exactly 2 tiles), and a null tile
 selected for matching.

 Tiles are their own basic JS objects, using a string id, their assigned icon as a string, whether the
 tile is "exposed" (showing its assigned symbol), and whether it has been matched for this game.

 Clicking a tile has several behaviors depending on conditions:
  - If a tile is clicked while no tile is exposed, then that tile is exposed, and marked as the tile 
    attempting to be matched by the user.  The attempt count is also incremented.
  - If a tile is clicked while another tile is attempting to be matched, then the tile is exposed.
    The game then performs matching logic below, after which it resets the tile attempting to be matched to null.
    * If the tile matches the previous-selected tile, both tiles are marked matched.
    * If not a match, the tiles remain exposed until the user attempts a new match pair.
    * On last match, display a message to the user, informing him that he won.

 The Give Up button exposes all tiles.
 The Reset button re-initializes the game.

 Several methods are used to relate which css classes tiles should be depending on certain game and/or tile data.
 - If a tile is the selected tile attempting to be matched, a css class is returned to highlight this tile as such.
   It will have a unique border or background indicated it was selected, and it will expose its glyph.
 - If a tile has been matched, a css class signals this to the user, using a green border and keeping the tile exposed.
 - If a pair of tiles were selected that did not match, they will receive a red border indicating such and remain exposed
   until the next attempt is made by the user.

 Angular.js bindings allow these css classes to be easily updated in the DOM, without writing code for polling or event
 listeners.  It also allows button bindings to methods within a given scope.  For this game, everything falls within the
 same scope; however, we could potentially put multiple games on the same page, using different controllers, possibly
 with methods with the same names.

 The corresponding HTML references several Angular method/data bindings, plus uses the following Angular methods/functions:
 - ng-app
 - ng-controller
 - ng-submit
 - ng-repeat
 - ng-switch
*/

angular.module('myGame', [])
 .controller('gameController', ['$scope', '$interval', '$http', function ($scope, $interval, $http) {

     var Timer = function () {
         var timer = this;
         var startTime = new Date();
         var time = 0;
         var timeout = null;
         timer.reset = function () {
             this.time = 0;
             $interval.cancel(timer.timeout);
         }

         timer.start = function () {
             timer.startTime = new Date();
             timer.timeout = $interval(timer.updateTime, 50);
         }

         timer.updateTime = function () {
             var now = new Date();
             timer.time = (now.getTime() - timer.startTime.getTime()) / 1000;
         }

         timer.stop = function () {
             if (angular.isDefined(timer.timeout)) {
                 $interval.cancel(timer.timeout);
                 //timer.timeout = undefined;
             }
             timer.updateTime();
         }

         timer.getTime = function () {
             if (timer.time == 0 || timer.time == null) {
                 return "0.000";
             }
             var out = parseFloat(timer.time).toFixed(3);
             return out;
         }

         timer.getTimeNbr = function () {
             return timer.time;
         }
     }

     /*********************************************************************/
     // constructor
     var game = this;

     /*********************************************************************/
     // return the message to the user - basically indicates the state of the state machine
     game.userMessage = function () {
         return game.message;
     }

     /*********************************************************************/
     // get a random tile index     
     game.randomTileNbr = function (max) {
         min = Math.ceil(0);
         max = Math.floor(max);
         return Math.floor(Math.random() * (max));
     }

     /*********************************************************************/
     // get tile object by id
     game.getTile = function (id) {
         var result = game.tiles.filter(function (obj) {
             return obj.id == id;
         });
         return result[0];
     }

     /*********************************************************************/
     // get tiles that have not been initialized
     game.getUnsetTiles = function () {
         var result = game.tiles.filter(function (obj) {
             return obj.icon == "";
         });
         return result;
     }

     /*********************************************************************/
     // get tiles that have not been initialized
     game.getUnmatchedTiles = function () {
         var result = game.tiles.filter(function (obj) {
             return obj.matched == false;
         });
         return result;
     }

     /*********************************************************************/
     // fetch all scores from web service
     game.fetchScores = function () {
         game.scores = new Array();
         var url = "getScores.php";
         $http.post(url).then(function (response) {
             game.scores = response.data;
         }, function (response) {
             alert("Failed to fetch high scores; response:" + response);
         });
     }

     /*********************************************************************/
     // initialize game
     game.init = function () {
         game.message = "Welcome, friend! Click a tile to begin";
         game.tileExposed = "";
         game.attempts = 0;
         game.timer = new Timer();
         game.tiles = [{ id: "tile1", icon: "", exposed: false, matched: false },
         { id: "tile2", icon: "", exposed: false, matched: false },
         { id: "tile3", icon: "", exposed: false, matched: false },
         { id: "tile4", icon: "", exposed: false, matched: false },
         { id: "tile5", icon: "", exposed: false, matched: false },
         { id: "tile6", icon: "", exposed: false, matched: false },
         { id: "tile7", icon: "", exposed: false, matched: false },
         { id: "tile8", icon: "", exposed: false, matched: false },
         { id: "tile9", icon: "", exposed: false, matched: false },
         { id: "tile10", icon: "", exposed: false, matched: false },
         { id: "tile11", icon: "", exposed: false, matched: false },
         { id: "tile12", icon: "", exposed: false, matched: false }];

         // this is the list of glyphicons that can be assigned to the tiles 
         var iconList = ["qrcode", "barcode", "font", "check", "indent-left", "play"];

         // randomly assign each icon to a pair of tiles
         for (var i = 0; i < 6; i++) {
             for (var j = 0; j < 2; j++) {
                 var unsetTiles = game.getUnsetTiles();
                 var tileNbr = game.randomTileNbr(unsetTiles.length);
                 var thisTile = game.getTile(unsetTiles[tileNbr].id);
                 thisTile.icon = "glyphicon-" + iconList[i];

                 // debug
                 // game.message = game.message + "*** tile:" + thisTile.id + ", icon:" + iconList[i] + "***  ";
             }
         }

         game.scores = game.fetchScores();
     }

     // call init by default when creating the controller
     game.init();

     /*********************************************************************/
     // return the css class for the tile if exposed 
     game.tileExposedClass = function (id) {
         var tile = game.getTile(id);
         if (tile.exposed == false) {
             return "";
         } else {
             if (game.tileExposed == id) {
                 return "exposedOneSelected";
             } else {
                 if (tile.matched == true) {
                     return "exposedMatch";
                 } else {
                     return "exposedNoMatch";
                 }
             }
         }
         return "";
     }

     /*********************************************************************/
     // return the css class for the tile if exposed 
     game.tileIcon = function (id) {
         var tile = game.getTile(id);
         if (tile.exposed == false) {
             // return "glyphicon-eye-open";
             return "glyphicon-question-sign";
         } else {
             return tile.icon;
         }
         return "";
     }

     /*********************************************************************/
     // upload a winning score to the server
     game.uploadScore = function () {
         var name = prompt("Please enter your name to submit your score! (max length: 10)", "");
         if (name != null) {
             name = name.substr(0, 10);
             var url = "postScore.php";
             var data = JSON.stringify({ name: name, attempts: game.attempts, seconds: game.timer.getTimeNbr() });
             $http.post(url, data, { 'Content-Type': 'application/x-www-form-urlencoded' })
                 .then(function (response) {
                     game.message = response.data;
                     game.fetchScores();
                 }, function (response) {
                     alert("Failed to upload score; response:" + response.data);
                 });
         }
     }

     /*********************************************************************/
     // core logic of game - handle a tile being pressed
     game.tilePressed = function (id) {
         // debug 
         //game.message = "pressed " + id;      

         // ignore pressing same tile
         if (game.tileExposed != id) {

             // reset pair of unmatched tiles if out there;
             var unmatchedTiles = game.getUnmatchedTiles();
             var unmatchedExposedTiles = unmatchedTiles.filter(function (obj) {
                 return obj.exposed == true;
             });
             if (unmatchedExposedTiles.length > 1) {
                 for (i = 0; i < unmatchedExposedTiles.length; i++) {
                     unmatchedExposedTiles[i].exposed = false;
                 }
             }

             var tile = game.getTile(id);
             if (game.tileExposed != "") {
                 var firstTile = game.getTile(game.tileExposed);
                 tile.exposed = true;
                 if (tile.icon == firstTile.icon) {
                     tile.matched = true;
                     firstTile.matched = true;
                     game.message = "Great work - you've found a match!";

                     unmatchedTiles = game.getUnmatchedTiles();
                     if (unmatchedTiles.length == 0) {
                         game.message = "MUCH AMAZE! U R WINNER! Please play again.";
                         game.timer.stop();
                         game.uploadScore();
                     }
                 } else {
                     // firstTile.exposed = false;
                     game.message = "No match - keep trying...U CAN DO IT.";
                 }
                 game.tileExposed = "";
             } else {
                 game.tileExposed = id;
                 tile.exposed = true;
                 game.message = "Tile selected - guess the matching tile";
                 game.attempts++;

                 if (game.attempts == 1) {
                     game.timer.start();
                 }
             }
         }
     }

     /*********************************************************************/
     // return true if tile has been matched
     game.tileMatched = function (id) {
         return game.getTile(id).matched;
     }

     /*********************************************************************/
     // give up - expose all tiles and scold user
     game.giveUp = function () {
         var won = true;
         for (var i = 0; i < game.tiles.length; i++) {
             if (game.tiles[i].matched != true) {
                 won = false;
                 break;
             }
         }

         if (won == false) {
             for (var i = 0; i < game.tiles.length; i++) {
                 game.tiles[i].matched = true;
             }
             game.message = "I'm not mad...I'm just disappointed.  Play again!";
             game.timer.stop();
         }
     }

     /*********************************************************************/
     // reset game
     game.reset = function () {
         game.init();
         game.message = "Game Reset - Better luck this time!";
     }

     /*********************************************************************/
     // retrieve attempts
     game.getAttempts = function () {
         return game.attempts;
     }

     /*********************************************************************/
     // helper for angular to get the full list of scores
     game.getScores = function () {
         return game.scores;
     }
 }])